package javaproject;

import java.util.Iterator;
import java.util.LinkedList;

public class AI {
    private static int agentNumbers=5;
    private LinkedList<Bullet> bulletList;
    private LinkedList<Capsule> capsuleList;
    private Snake snake;
    private boolean active;
    private Debugger debugger;
    // for learning
    static boolean hitBullet=false;
    static int lastDecideAgent;
    static double[] confidence_multiplier = new double[agentNumbers];
    private Decision[] decision = new Decision[agentNumbers];
    private int lastNum;
    private double W,H;
    
    class Decision{
        public int direction;
        public double confidence;
        public Decision(int direction, double confidence)
        {
            this.direction = direction;
            this.confidence = confidence;
        }
    }
    
    //決策
    public AI(LinkedList<Bullet> bulletList, LinkedList<Capsule> capsuleList, Snake snake, Debugger debugger)
    {
        this.active = true;
        this.bulletList = bulletList;
        this.capsuleList = capsuleList;
        this.snake = snake;
        this.debugger = debugger;
        this.W = Config.hitRadius*5;
        this.H = Config.hitRadius*5;
        this.lastNum = 0;
    }
    public void proceed()
    {
        if(active)
        {
            double max_confidence = 0;
            int max_direction = Snake.NONE;
            //int agentNumbers = 5;
            double[] confidence = new double[agentNumbers];
            //Decision[] decision = new Decision[agentNumbers];
            //double[] confidence_multiplier = new double[agentNumbers];
            int index = 0;
            
            // for learning
            if (hitBullet)
            {
                if (lastDecideAgent != 0)
                    confidence_multiplier[lastDecideAgent] -= 0.02;
                //System.out.println(lastDecideAgent+" Q_Q");
                hitBullet = false;
                //System.out.println(confidence_multiplier[0]+" , "+confidence_multiplier[1]+" , "
                //   +confidence_multiplier[2]+" , "+confidence_multiplier[3]+" , "+confidence_multiplier[4]);
            }
            
            decision[0] = wallAvoid();
            decision[1] = avoidCloseBullet();
            decision[2] = findBlankArea();
            decision[3] = goCentral();
            decision[4] = eatCapsule();
            
            for(int i = 0; i < agentNumbers; i++)
            {
                confidence[i] = decision[i].confidence*confidence_multiplier[i];
                if(confidence[i] > max_confidence)
                {
                    max_confidence = confidence[i];
                    max_direction = decision[i].direction;
                    index = i;
                }
            }
            lastDecideAgent = index;
            //System.out.println(confidence[0]+" , "+confidence[1]+" , "
            //       +confidence[2]+" , "+confidence[3]+" , "+confidence[4]+" _"+index);
            //System.out.print(index+" ");
            
            debugger.update(debugger.T_rotX, index);
            debugger.update(debugger.T_roll, decision[0].confidence);
            debugger.update(debugger.T_pitch, decision[1].confidence);
            debugger.update(debugger.T_realX, decision[2].confidence);
            debugger.update(debugger.T_realY, decision[3].confidence);
            debugger.update(debugger.T_realZ, decision[4].confidence);
            
            //修正閃牆
            double[] pos = snake.getPosition();
            if(decision[0].direction == Snake.RIGHT)
            {
                if(Config.boundingW+pos[0] > Config.boundingW/6 && Config.boundingW+pos[0] < Config.boundingW/5)
                {
                    if(max_direction == Snake.UP_LEFT)
                        max_direction = Snake.UP;
                    else if(max_direction == Snake.DOWN_LEFT)
                        max_direction = Snake.DOWN;
                }
                else if(Config.boundingW+pos[1] < Config.boundingW/6)
                    max_direction = Snake.RIGHT;
            }
            else if(decision[0].direction == Snake.LEFT)
            {
                if(Config.boundingW-pos[0] > Config.boundingW/6 && Config.boundingW-pos[0] < Config.boundingW/5)
                {
                    if(max_direction == Snake.UP_LEFT)
                        max_direction = Snake.UP;
                    else if(max_direction == Snake.DOWN_LEFT)
                        max_direction = Snake.DOWN;
                }
                else if(Config.boundingW-pos[0] < Config.boundingW/6)
                    max_direction = Snake.LEFT;
            }
            else if(decision[0].direction == Snake.UP)
            {
                if(Config.boundingH+pos[1] > Config.boundingH/6 && Config.boundingH+pos[1] < Config.boundingH/5)
                {
                    if(max_direction == Snake.DOWN_LEFT)
                        max_direction = Snake.LEFT;
                    else if(max_direction == Snake.DOWN_RIGHT)
                        max_direction = Snake.RIGHT;
                }
                else if(Config.boundingH+pos[1] < Config.boundingH/6)
                    max_direction = Snake.UP;
            }
            else if(decision[0].direction == Snake.DOWN)
            {
                if(Config.boundingH-pos[1] > Config.boundingH/6 && Config.boundingH-pos[1] < Config.boundingH/5)
                {
                    if(max_direction == Snake.UP_LEFT)
                        max_direction = Snake.LEFT;
                    else if(max_direction == Snake.UP_RIGHT)
                        max_direction = Snake.RIGHT;
                }
                else if(Config.boundingH-pos[1] < Config.boundingH/6)
                    max_direction = Snake.DOWN;
            }
            double v[] = snake.dirToVel(max_direction);
            snake.setV(v[0], v[1], v[2]);
        }
    }
    public double cartisianSum(double x, double y, int direction)
    {
        double[] pos = snake.getPosition();
        switch(direction)
        {
            case Snake.UP:
                return Math.sqrt(Math.pow(pos[0]-x,2)+Math.pow(pos[1]-y+Config.moveY,2));
            case Snake.DOWN:
                return Math.sqrt(Math.pow(pos[0]-x,2)+Math.pow(pos[1]-y-Config.moveY,2));
            case Snake.LEFT:
                return Math.sqrt(Math.pow(pos[0]-x-Config.moveX,2)+Math.pow(pos[1]-y,2));
            case Snake.RIGHT:
                return Math.sqrt(Math.pow(pos[0]-x+Config.moveX,2)+Math.pow(pos[1]-y,2));
            case Snake.UP_RIGHT:
                return Math.sqrt(Math.pow(pos[0]-x+Config.moveX,2)+Math.pow(pos[1]-y+Config.moveY,2));
            case Snake.UP_LEFT:
                return Math.sqrt(Math.pow(pos[0]-x-Config.moveX,2)+Math.pow(pos[1]-y+Config.moveY,2));
            case Snake.DOWN_RIGHT:
                return Math.sqrt(Math.pow(pos[0]-x+Config.moveX,2)+Math.pow(pos[1]-y-Config.moveY,2));
            case Snake.DOWN_LEFT:
                return Math.sqrt(Math.pow(pos[0]-x-Config.moveX,2)+Math.pow(pos[1]-y-Config.moveY,2));
            default:
                return Math.sqrt(Math.pow(pos[0]-x,2)+Math.pow(pos[1]-y,2));
        }
    }
    private Decision wallAvoid()
    {
        double pos[] = snake.getPosition();
        double avoidThreshold = +Config.moveX*50;

        if(pos[0] < -Config.boundingW+avoidThreshold)
            return new Decision(Snake.RIGHT,1-(pos[0]+Config.boundingW)/avoidThreshold);
        else if(pos[0] > Config.boundingW-avoidThreshold)
            return new Decision(Snake.LEFT,1-(Config.boundingW-pos[0])/avoidThreshold);
        else if(pos[1] > Config.boundingH-avoidThreshold)
            return new Decision(Snake.DOWN,1-(Config.boundingH-pos[1])/avoidThreshold);
        else if(pos[1] < -Config.boundingH+avoidThreshold)
            return new Decision(Snake.UP,1-(pos[1]+Config.boundingH)/avoidThreshold);
        
        return new Decision(Snake.NONE, 0);
    }
    private Decision goCentral()
    {
            double[] pos = snake.getPosition();
            double distance = Math.sqrt(pos[0]*pos[0]+pos[1]*pos[1]+pos[2]*pos[2]);
            double maximumDistance = Math.sqrt(Config.boundingW*Config.boundingW+
                                              Config.boundingH*Config.boundingH+
                                              Config.boundingD*Config.boundingD);
            if (pos[0] > 0)
            {
                if (pos[1] > 0)
                    return new Decision(Snake.DOWN_LEFT, distance/maximumDistance);
                else
                    return new Decision(Snake.UP_LEFT, distance/maximumDistance);
            }
            else
            {
                if (pos[1] > 0)
                    return new Decision(Snake.DOWN_RIGHT, distance/maximumDistance);
                else
                    return new Decision(Snake.UP_RIGHT, distance/maximumDistance);
            }
    }
    private Decision findBlankArea()
    {
        int divide = 4;
        int[] areas = new int[divide*divide];
        Iterator it = bulletList.iterator();
        double areaW = 2 * Config.boundingW / divide;
        double areaH = 2 * Config.boundingH / divide;
        
        for (int i=0; i<divide*divide; i++)  areas[i]=0;
        
        double x, y;
        int index;
        while(it.hasNext())
        {
            Bullet b = (Bullet)it.next();
            x = b.x;
            y = b.y;
            index = (int)((x+Config.boundingW)/areaW)*divide + (int)((y+Config.boundingW)/areaH);
            if (index < divide*divide && index >= 0)
                areas[index] = areas[index] + 1;
        }
        
        int min=0;
        for (int i=0; i<divide*divide; i++)
        {
            //System.out.print(areas[i]+" ");                                       ////////////////////
            if (areas[i] < areas[min])  min=i;
        }
        //System.out.println("\nmin area: "+ index/divide + "," + index%divide);      ///////////////////
        
        double pos[] = snake.getPosition();
        int areaX = (int)((pos[0]+Config.boundingW)/areaW);
        int areaY = (int)((pos[1]+Config.boundingH)/areaH);
        //System.out.println("now: "+ areaX + "," + areaY);                         ////////////////////
        
        int minX = min/divide;
        int minY = min%divide;
        double confidence = 1 - Math.pow((double)((areaX-minX)*(areaX-minX)+(areaY-minY)*(areaY-minY))/(2*divide*divide), 0.3);
        
        if (areaX > minX)
        {
            if (areaY > minY)
                return new Decision(Snake.DOWN_LEFT,confidence);
            else if (areaY < minY)
                return new Decision(Snake.UP_LEFT,confidence);
            else
                return new Decision(Snake.LEFT,confidence);
        }
        else if (areaX < minX)
        {
            if (areaY > minY)
                return new Decision(Snake.DOWN_RIGHT,confidence);
            else if (areaY < minY)
                return new Decision(Snake.UP_RIGHT,confidence);
            else
                return new Decision(Snake.RIGHT,confidence);
        }
        else
        {
            if (areaY > minY)
                return new Decision(Snake.DOWN,confidence);
            else if (areaY < minY)
                return new Decision(Snake.UP,confidence);
            else
                return new Decision(Snake.NONE, 0);
        }
    }
    private Decision avoidCloseBullet()
    {
        int divide = 3;
        int[] areas = new int[divide*divide];
        Iterator it = bulletList.iterator();
        double areaW = Config.hitRadius*6;
        double areaH = Config.hitRadius*6;
        
        for (int i=0; i<divide*divide; i++)  areas[i]=0;
        
        // use current position as central
        double pos[] = snake.getPosition();
        double maximumDistance = Math.sqrt(areaW*areaW+areaH*areaH);
        double minDistance = 999;
        
        double[] sumV = {0,0,0};
        //共有幾個夠靠近的子彈
        int closeNum = 0;
        while(it.hasNext())
        {
            Bullet b = (Bullet)it.next();

            if ((b.x < pos[0]+areaW*1/2) && (b.x > pos[0]-areaW*1/2)
             && (b.y < pos[1]+areaH*1/2) && (b.y > pos[1]-areaW*1/2))
            {
                double distance = Math.sqrt((b.x-pos[0])*(b.x-pos[0])+(b.y-pos[1])*(b.y-pos[1]));
                if(distance < minDistance)
                    minDistance = distance;
                
                double vx = pos[0]-b.x;
                double vy = pos[1]-b.y;
                double vz = pos[2]-b.z;

                double normal_vx1 = b.vy;
                double normal_vy1 = -b.vx;
                double normal_vx2 = -b.vy;
                double normal_vy2 = b.vx;
                double dot1 = vx*normal_vx1+vy*normal_vy1;
                double dot2 = vx*normal_vx2+vy*normal_vy2;
                
                double priority = Math.pow(1-(distance/maximumDistance), 2);
                
                //太近時往反方向
                if(distance < Config.radius*2)
                {
                    double[] v = normalize(pos[0]-b.x, pos[1]-b.y, 0);
                    sumV[0] += v[0]*priority;
                    sumV[1] += v[1]*priority;
                }

                //內積越大的方向離子但越遠
                if(dot1 > dot2)
                {
                    double[] v = normalize(normal_vx1, normal_vy1, 0);
                    sumV[0] += v[0]*priority;
                    sumV[1] += v[1]*priority;
                    //return new Decision(Snake.velToDir(normal_vx1, normal_vy1, 0), Math.pow((1-minDistance/maximumDistance), 2));
                }
                else
                {
                    double[] v = normalize(normal_vx2, normal_vy2, 0);
                    sumV[0] += v[0]*priority;
                    sumV[1] += v[1]*priority;
                }
                    /*
                }
                else
                {
                    double[] v = normalize(pos[0]-b.x, pos[1]-b.y, 0);
                    sumV[0] += v[0]*priority;
                    sumV[1] += v[1]*priority;
                }*/
                closeNum++;
            }
        }
        if(closeNum > lastNum)
        {
            for(int i = 0; i < closeNum-lastNum; i++)
            {
                /*
                W -= Config.hitRadius/2;
                H -= Config.hitRadius/2;
                * 
                */
            }
            lastNum = closeNum;
        }
        else if(closeNum < lastNum)
        {
            for(int i = 0; i < lastNum-closeNum; i++)
            {
                /*
                W += Config.hitRadius/2;
                H += Config.hitRadius/2;
                * 
                */
            }
            lastNum = closeNum;
        }
        if(closeNum > 0)
        {
            double[] v = normalize(sumV);
            return new Decision(Snake.velToDir(v[0], v[1], 0), Math.pow((1-minDistance/maximumDistance), 2));
        }
        else
            return new Decision(Snake.NONE, 0);
    }
    private Decision eatCapsule()
    {
        Iterator it = capsuleList.iterator();
        double min_dis = 10,
               max_bound = Math.sqrt(Config.boundingW*Config.boundingW+Config.boundingH*Config.boundingH);
        int index = 0;
        while(it.hasNext())
        {
            Capsule c = (Capsule)it.next();
            double dis[] = new double[9];
            double x = c.x,
                   y = c.y;
            if(x > Config.boundingH*2 || x < -Config.boundingH*2 || y > Config.boundingW*2 || y < -Config.boundingW*2)
                continue;
            dis[0] = cartisianSum(x, y, Snake.NONE);
            dis[1] = cartisianSum(x, y, Snake.UP);
            dis[2] = cartisianSum(x, y, Snake.DOWN);
            dis[3] = cartisianSum(x, y, Snake.LEFT);
            dis[4] = cartisianSum(x, y, Snake.RIGHT);
            dis[5] = cartisianSum(x, y, Snake.UP_RIGHT);
            dis[6] = cartisianSum(x, y, Snake.UP_LEFT);
            dis[7] = cartisianSum(x, y, Snake.DOWN_RIGHT);
            dis[8] = cartisianSum(x, y, Snake.DOWN_LEFT);
            for(int i=0; i<9; i++)
            {
                if(min_dis > dis[i])
                {
                    min_dis = dis[i];
                    index = i;
                }
            }
        }
        switch(index)
        {
            case 1:
                return new Decision(Snake.UP, Math.pow((1-min_dis/max_bound),0.5));
            case 2:
                return new Decision(Snake.DOWN, Math.pow((1-min_dis/max_bound),0.5));
            case 3:
                return new Decision(Snake.LEFT, Math.pow((1-min_dis/max_bound),0.5));
            case 4:
                return new Decision(Snake.RIGHT, Math.pow((1-min_dis/max_bound),0.5));
            case 5:
                return new Decision(Snake.UP_RIGHT, Math.pow((1-min_dis/max_bound),0.5));
            case 6:
                return new Decision(Snake.UP_LEFT, Math.pow((1-min_dis/max_bound),0.5));
            case 7:
                return new Decision(Snake.DOWN_RIGHT, Math.pow((1-min_dis/max_bound),0.5));
            case 8:
                return new Decision(Snake.DOWN_LEFT, Math.pow((1-min_dis/max_bound),0.5));
            default:
                return new Decision(Snake.NONE, Math.pow((1-min_dis/max_bound),0.5));
        }
    }
    public void setActive(boolean active)
    {
        this.active = active;
    }

    //回傳這個子彈的速度是不是面向你
    private boolean isFaced(Bullet bullet)
    {
        //幾度以內算是被面向
        double facingAngle = 30 *2*Math.PI/360;
        double[] pos = snake.getPosition();
        double[] v = {pos[0]-bullet.x,
                      pos[1]-bullet.y,
                      pos[2]-bullet.z};
        double dot = bullet.vx*v[0]+bullet.vy*v[1]+bullet.vz*v[2];
        double vLength = Math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
        double bulletVLength = Math.sqrt(bullet.vx*bullet.vx+bullet.vy*bullet.vy+bullet.vz*bullet.vz);
        double angle = Math.acos(dot/(vLength*bulletVLength));
        if(angle < facingAngle)
            return true;
        else
            return false;
    }
    private double[] normalize(double[] vector)
    {
        return normalize(vector[0], vector[1], vector[2]);
    }
    private double[] normalize(double x, double y, double z)
    {
        double length = Math.sqrt(x*x+y*y+z*z);
        double[] normalized = {x/length, y/length, z/length};
        return normalized;
    }
}
